package net.xuhf.utils;

import com.alibaba.fastjson.JSONObject;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import org.apache.commons.compress.utils.Lists;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class MinioUtil {

    @Resource
    private MinioClient minioClient;

    /**
     * 存储桶是否存在
     *
     * @param bucketName 存储桶名称
     * @return 存储桶是否存在
     */
    public boolean bucketExists(String bucketName) throws Exception {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建存储桶
     *
     * @param bucketName 存储桶名称
     * @return 是否创建成功
     */
    public boolean makeBucket(String bucketName) {
        try {
            //判断 存储桶存不存在
            boolean bucketExists = bucketExists(bucketName);
            if (!bucketExists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     * 查询全部存储桶信息
     *
     * @return 返回全部存储桶列表
     * @throws Exception
     */
    public List<Bucket> listBuckets() throws Exception {
        List<Bucket> listBuckets = minioClient.listBuckets();
        return listBuckets;
    }

    /**
     * 查询桶中的所有对象
     *
     * @param bucketName 存储桶名称
     * @throws Exception
     */
    public List<Map> listObjects(String bucketName) throws Exception {
        Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
        Iterator<Result<Item>> iterator = results.iterator();
        List<Map> items = Lists.newArrayList();
        String format = "{'fileName':'%s','fileSize':'%s','versionId':'%s','lastModified':'%s'}";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        while (iterator.hasNext()) {
            Item item = iterator.next().get();
            items.add(JSONObject.parseObject(String.format(format,
                    item.objectName(),
                    formatFileSize(item.size()),
                    item.versionId(),
                    item.lastModified().format(formatter)
            ), Map.class));
        }
        return items;
    }

    /**
     * 删除一个存储桶 注意，只有存储桶为空时才能删除成功。
     *
     * @param bucketName 存储桶名称
     * @throws Exception
     */
    public boolean removeBucket(String bucketName) {
        try {
            // 检查桶是否存在数据
            boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (found) {
                minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 获取对象的元数据
     *
     * @param bucketName 存储桶名称。
     * @param objectName 存储桶里的对象名称
     * @return 元数据信息
     * @throws Exception
     */
    public StatObjectResponse statObject(String bucketName, String objectName) throws Exception {
        StatObjectArgs build = StatObjectArgs.builder().bucket(bucketName).object(objectName).build();
        return minioClient.statObject(build);
    }

    /**
     * 获取桶内对象的下载流
     *
     * @param bucketName 存储桶名称。
     * @param objectName 存储桶里的对象名称
     * @return 对象的Input流
     * @throws Exception
     */
    public InputStream getObject(String bucketName, String objectName) throws Exception {
        GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucketName).object(objectName).build();
        return minioClient.getObject(getObjectArgs);
    }


    /**
     * 上传文件
     *
     * @param bucketName    存储桶名称
     * @param dir   文件夹目录
     * @param file  文件
     */
    public String putObject(String bucketName, String dir, MultipartFile file) {
        //获取文件名
        String fileName = file.getOriginalFilename();
        try (InputStream in = file.getInputStream()) {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(dir + fileName)
                    /**
                     * 设置要上传的流。 objectSize：上传的对象大小；partSize：分段上传的大小（大概理解为，大文件可以设置为分段上传，每段的大小），单位为byte
                     * 如果要上传的对象大小未知，则将 -1 传递给objectSize，并需要传递有效的partSize。
                     * 如果要上传的对象大小已知，可以将 -1 传递给partSize，系统会进行自动检测是否需要分段上传；也可以传递有效的 partSize 来控制对内存的使用。
                     * 如果 partSize 大于 objectSize，则 objectSize 将覆盖 partSize的值。
                     * partSize有效的取值大小介于 5MiB 到 5GiB 之间（前后闭区间）
                     */
                    .stream(in, file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build();
            minioClient.putObject(putObjectArgs);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dir + fileName;
    }

    /**
     * 上传文件
     *
     * @param bucketName 存储桶名称
     * @param file       文件
     */
    public String putObject(String bucketName, MultipartFile file) {
        //获取文件名
        String fileName = file.getOriginalFilename();
        try (InputStream in = file.getInputStream()) {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    /**
                     * 设置要上传的流。 objectSize：上传的对象大小；partSize：分段上传的大小（大概理解为，大文件可以设置为分段上传，每段的大小），单位为byte
                     * 如果要上传的对象大小未知，则将 -1 传递给objectSize，并需要传递有效的partSize。
                     * 如果要上传的对象大小已知，可以将 -1 传递给partSize，系统会进行自动检测是否需要分段上传；也可以传递有效的 partSize 来控制对内存的使用。
                     * 如果 partSize 大于 objectSize，则 objectSize 将覆盖 partSize的值。
                     * partSize有效的取值大小介于 5MiB 到 5GiB 之间（前后闭区间）
                     */
                    .stream(in, file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build();
            minioClient.putObject(putObjectArgs);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fileName;
    }

    /**
     * 上传文件
     *
     * @param bucketName 存储桶名称
     * @param file       文件
     */
    public List<String> putObjects(String bucketName, MultipartFile... file) {
        List<String> path = Lists.newArrayList();
        for (MultipartFile multipartFile : file) {
            //获取文件名
            String fileName = multipartFile.getOriginalFilename();
            path.add(fileName);
            try (InputStream in = multipartFile.getInputStream()) {
                PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        /**
                         * 设置要上传的流。 objectSize：上传的对象大小；partSize：分段上传的大小（大概理解为，大文件可以设置为分段上传，每段的大小），单位为byte
                         * 如果要上传的对象大小未知，则将 -1 传递给objectSize，并需要传递有效的partSize。
                         * 如果要上传的对象大小已知，可以将 -1 传递给partSize，系统会进行自动检测是否需要分段上传；也可以传递有效的 partSize 来控制对内存的使用。
                         * 如果 partSize 大于 objectSize，则 objectSize 将覆盖 partSize的值。
                         * partSize有效的取值大小介于 5MiB 到 5GiB 之间（前后闭区间）
                         */
                        .stream(in, multipartFile.getSize(), -1)
                        .contentType(multipartFile.getContentType())
                        .build();
                minioClient.putObject(putObjectArgs);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return path;
    }

    /**
     * 将对象的数据下载到本地文件中
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     * @param fileName   本地文件全限定名
     * @throws Exception
     */
    public void downloadObject(String bucketName, String objectName, String fileName) throws Exception {
        DownloadObjectArgs downloadObjectArgs = DownloadObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .filename(fileName)
                .build();
        minioClient.downloadObject(downloadObjectArgs);
    }

    /**
     * 删除桶内的文件
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     * @throws Exception
     */
    public boolean removeObject(String bucketName, String objectName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 生成一个给HTTP请求带签名的URL,可以指定了 HTTP 方法、到期时间和自定义请求参数。
     * 浏览器/移动端的客户端可以用这个URL进行下载，
     * 即使其所在的存储桶是私有的。
     * 这个presigned URL可以设置一个失效时间，默认值是7天
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     * @param expiry     失效时间(单位小时)
     * @param unit       失效时间单位,TimeUnit.HOURS
     * @return
     */
    public String getPresignedObjectUrl(String bucketName, String objectName, Integer expiry, TimeUnit unit) throws Exception {
        GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(bucketName)
                .object(objectName)
                .expiry(expiry, unit)
                .build();
        return minioClient.getPresignedObjectUrl(build);
    }

    private static String formatFileSize(long fileS) {
        DecimalFormat df = new DecimalFormat("#.00");
        String fileSizeString = "";
        String wrongSize = "0B";
        if (fileS == 0) {
            return wrongSize;
        }
        if (fileS < 1024) {
            fileSizeString = df.format((double) fileS) + " B";
        } else if (fileS < 1048576) {
            fileSizeString = df.format((double) fileS / 1024) + " KB";
        } else if (fileS < 1073741824) {
            fileSizeString = df.format((double) fileS / 1048576) + " MB";
        } else {
            fileSizeString = df.format((double) fileS / 1073741824) + " GB";
        }
        return fileSizeString;
    }
}
